package gagak.framework.web.support.http;

import gagak.framework.web.exception.InvalidMediaTypeException;
import gagak.framework.web.exception.InvalidMimeTypeException;
import gagak.framework.web.utils.MimeTypeUtils;
import gagak.framework.web.annonation.Nullable;
import gagak.framework.web.utils.Assert;
import gagak.framework.web.utils.StringUtils;
import org.apache.commons.collections.CollectionUtils;

import java.util.*;

/*
 * @program: ce
 * @description: 媒体类型封装
 * @author: houhong
 * @create: 2022-11-25 22:09
 */
public class MediaType extends MimeType {

    private static final long serialVersionUID = 2069937152339670231L;

    /*
     * Public constant media type that includes all media ranges (i.e. "&#42;/&#42;").
     */
    public static final MediaType ALL;

    /*
     * A String equivalent of {@link MediaType#ALL}.
     */
    public static final String ALL_VALUE = "*/*";

    /*
     * Public constant media type for {@code application/atom+xml}.
     */
    public static final MediaType APPLICATION_ATOM_XML;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_ATOM_XML}.
     */
    public static final String APPLICATION_ATOM_XML_VALUE = "application/atom+xml";

    /*
     * Public constant media type for {@code application/x-www-form-urlencoded}.
     */
    public static final MediaType APPLICATION_FORM_URLENCODED;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_FORM_URLENCODED}.
     */
    public static final String APPLICATION_FORM_URLENCODED_VALUE = "application/x-www-form-urlencoded";

    /*
     * Public constant media type for {@code application/json}.
     */
    public static final MediaType APPLICATION_JSON;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_JSON}.
     *
     * @see #APPLICATION_JSON_UTF8_VALUE
     */
    public static final String APPLICATION_JSON_VALUE = "application/json";

    /*
     * Public constant media type for {@code application/json;charset=UTF-8}.
     * <p>This {@link MediaType#APPLICATION_JSON} variant should be used to set JSON
     * content type because while
     * <a href="https://tools.ietf.org/html/rfc7159#section-11">RFC7159</a>
     * clearly states that "no charset parameter is defined for this registration", some
     * browsers require it for interpreting correctly UTF-8 special characters.
     */
    public static final MediaType APPLICATION_JSON_UTF8;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_JSON_UTF8}.
     * <p>This {@link MediaType#APPLICATION_JSON_VALUE} variant should be used to set JSON
     * content type because while
     * <a href="https://tools.ietf.org/html/rfc7159#section-11">RFC7159</a>
     * clearly states that "no charset parameter is defined for this registration", some
     * browsers require it for interpreting correctly UTF-8 special characters.
     */
    public static final String APPLICATION_JSON_UTF8_VALUE = "application/json;charset=UTF-8";

    /*
     * Public constant media type for {@code application/octet-stream}.
     */
    public static final MediaType APPLICATION_OCTET_STREAM;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_OCTET_STREAM}.
     *  表示是一个未知程序
     */
    public static final String APPLICATION_OCTET_STREAM_VALUE = "application/octet-stream";

    /*
     * Public constant media type for {@code application/pdf}.
     *
     * @since 4.3
     */
    public static final MediaType APPLICATION_PDF;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_PDF}.
     *
     * @since 4.3
     */
    public static final String APPLICATION_PDF_VALUE = "application/pdf";

    /*
     * Public constant media type for {@code application/problem+json}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
     * Problem Details for HTTP APIs, 6.1. application/problem+json</a>
     * @since 5.0
     */
    public static final MediaType APPLICATION_PROBLEM_JSON;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON}.
     *
     * @since 5.0
     */
    public static final String APPLICATION_PROBLEM_JSON_VALUE = "application/problem+json";

    /*
     * Public constant media type for {@code application/problem+json}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.1">
     * Problem Details for HTTP APIs, 6.1. application/problem+json</a>
     * @since 5.0
     */
    public static final MediaType APPLICATION_PROBLEM_JSON_UTF8;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_JSON_UTF8}.
     *
     * @since 5.0
     */
    public static final String APPLICATION_PROBLEM_JSON_UTF8_VALUE = "application/problem+json;charset=UTF-8";

    /*
     * Public constant media type for {@code application/problem+xml}.
     *
     * @see <a href="https://tools.ietf.org/html/rfc7807#section-6.2">
     * Problem Details for HTTP APIs, 6.2. application/problem+xml</a>
     * @since 5.0
     */
    public static final MediaType APPLICATION_PROBLEM_XML;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_PROBLEM_XML}.
     *
     * @since 5.0
     */
    public static final String APPLICATION_PROBLEM_XML_VALUE = "application/problem+xml";

    /*
     * Public constant media type for {@code application/rss+xml}.
     *
     * @since 4.3.6
     */
    public static final MediaType APPLICATION_RSS_XML;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_RSS_XML}.
     *
     * @since 4.3.6
     */
    public static final String APPLICATION_RSS_XML_VALUE = "application/rss+xml";

    /*
     * Public constant media type for {@code application/stream+json}.
     *
     * @since 5.0
     */
    public static final MediaType APPLICATION_STREAM_JSON;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_STREAM_JSON}.
     *
     * @since 5.0
     */
    public static final String APPLICATION_STREAM_JSON_VALUE = "application/stream+json";

    /*
     * Public constant media type for {@code application/xhtml+xml}.
     */
    public static final MediaType APPLICATION_XHTML_XML;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_XHTML_XML}.
     */
    public static final String APPLICATION_XHTML_XML_VALUE = "application/xhtml+xml";

    /*
     * Public constant media type for {@code application/xml}.
     */
    public static final MediaType APPLICATION_XML;

    /*
     * A String equivalent of {@link MediaType#APPLICATION_XML}.
     */
    public static final String APPLICATION_XML_VALUE = "application/xml";

    /*
     * Public constant media type for {@code image/gif}.
     */
    public static final MediaType IMAGE_GIF;

    /*
     * A String equivalent of {@link MediaType#IMAGE_GIF}.
     */
    public static final String IMAGE_GIF_VALUE = "image/gif";

    /*
     * Public constant media type for {@code image/jpeg}.
     */
    public static final MediaType IMAGE_JPEG;

    /*
     * A String equivalent of {@link MediaType#IMAGE_JPEG}.
     */
    public static final String IMAGE_JPEG_VALUE = "image/jpeg";

    /*
     * Public constant media type for {@code image/png}.
     */
    public static final MediaType IMAGE_PNG;

    /*
     * A String equivalent of {@link MediaType#IMAGE_PNG}.
     */
    public static final String IMAGE_PNG_VALUE = "image/png";


    public static final MediaType MULTIPART_FORM_DATA;

    public static final String MULTIPART_FORM_DATA_VALUE = "multipart/form-data";


    public static final MediaType TEXT_EVENT_STREAM;


    public static final String TEXT_EVENT_STREAM_VALUE = "text/event-stream";


    public static final MediaType TEXT_HTML;


    public static final String TEXT_HTML_VALUE = "text/html";


    public static final MediaType TEXT_MARKDOWN;


    public static final String TEXT_MARKDOWN_VALUE = "text/markdown";

    public static final MediaType TEXT_PLAIN;


    public static final String TEXT_PLAIN_VALUE = "text/plain";

    public static final MediaType TEXT_XML;


    public static final String TEXT_XML_VALUE = "text/xml";

    private static final String PARAM_QUALITY_FACTOR = "q";


    static {
        ALL = (MediaType) valueOf(ALL_VALUE);
        APPLICATION_ATOM_XML = (MediaType) valueOf(APPLICATION_ATOM_XML_VALUE);
        APPLICATION_FORM_URLENCODED = (MediaType) valueOf(APPLICATION_FORM_URLENCODED_VALUE);
        APPLICATION_JSON = (MediaType) valueOf(APPLICATION_JSON_VALUE);
        APPLICATION_JSON_UTF8 = (MediaType) valueOf(APPLICATION_JSON_UTF8_VALUE);
        APPLICATION_OCTET_STREAM = (MediaType) valueOf(APPLICATION_OCTET_STREAM_VALUE);
        APPLICATION_PDF = (MediaType) valueOf(APPLICATION_PDF_VALUE);
        APPLICATION_PROBLEM_JSON = (MediaType) valueOf(APPLICATION_PROBLEM_JSON_VALUE);
        APPLICATION_PROBLEM_JSON_UTF8 = (MediaType) valueOf(APPLICATION_PROBLEM_JSON_UTF8_VALUE);
        APPLICATION_PROBLEM_XML = (MediaType) valueOf(APPLICATION_PROBLEM_XML_VALUE);
        APPLICATION_RSS_XML = (MediaType) valueOf(APPLICATION_RSS_XML_VALUE);
        APPLICATION_STREAM_JSON = (MediaType) valueOf(APPLICATION_STREAM_JSON_VALUE);
        APPLICATION_XHTML_XML = (MediaType) valueOf(APPLICATION_XHTML_XML_VALUE);
        APPLICATION_XML = (MediaType) valueOf(APPLICATION_XML_VALUE);
        IMAGE_GIF = (MediaType) valueOf(IMAGE_GIF_VALUE);
        IMAGE_JPEG = (MediaType) valueOf(IMAGE_JPEG_VALUE);
        IMAGE_PNG = (MediaType) valueOf(IMAGE_PNG_VALUE);
        MULTIPART_FORM_DATA = (MediaType) valueOf(MULTIPART_FORM_DATA_VALUE);
        TEXT_EVENT_STREAM = (MediaType) valueOf(TEXT_EVENT_STREAM_VALUE);
        TEXT_HTML = (MediaType) valueOf(TEXT_HTML_VALUE);
        TEXT_MARKDOWN = (MediaType) valueOf(TEXT_MARKDOWN_VALUE);
        TEXT_PLAIN = (MediaType) valueOf(TEXT_PLAIN_VALUE);
        TEXT_XML = (MediaType) valueOf(TEXT_XML_VALUE);
    }

    public MediaType(String type) {
        super(type);
    }

    public MediaType(MediaType other, @Nullable Map<String, String> parameters) {
        super(other.getType(), other.getSubtype(), parameters);
    }

    public MediaType(String type, String subtype, Map<String, String> parameters) {
        super(type, subtype, parameters);
    }

    public static void sortBySpecificity(List<MediaType> mediaTypes) {
        Assert.notNull(mediaTypes, "'mediaTypes' must not be null");
        if (mediaTypes.size() > 1) {
            //mediaTypes.sort(SPECIFICITY_COMPARATOR);
        }
    }

    public MediaType copyQualityValue(MediaType mediaType) {
        if (!mediaType.getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
            return this;
        }
        Map<String, String> params = new LinkedHashMap<String, String>(getParameters());
        params.put(PARAM_QUALITY_FACTOR, mediaType.getParameters().get(PARAM_QUALITY_FACTOR));
        return new MediaType(this, params);
    }

    public static String toString(List<MediaType> acceptableMediaTypes) {

        return null;
    }

    public static List<MediaType> parseMediaTypes(List<String> mediaTypes) {
        if (CollectionUtils.isEmpty(mediaTypes)) {
            return Collections.emptyList();
        } else if (mediaTypes.size() == 1) {
            return parseMediaTypes(mediaTypes.get(0));
        } else {
            List<MediaType> result = new ArrayList<MediaType>(8);
            for (String mediaType : mediaTypes) {
                result.addAll(parseMediaTypes(mediaType));
            }
            return result;
        }
    }

    public static List<MediaType> parseMediaTypes(String mediaTypes) {
        if (!StringUtils.hasLength(mediaTypes)) {
            return Collections.emptyList();
        }
        String[] tokens = StringUtils.tokenizeToStringArray(mediaTypes, ",");
        List<MediaType> result = new ArrayList<MediaType>(tokens.length);
        for (String token : tokens) {
            result.add(parseMediaType(token));
        }
        return result;
    }

    public static MediaType parseMediaType(String mediaType) {
        MimeType type;
        try {
            type = MimeTypeUtils.parseMimeType(mediaType);
        } catch (InvalidMimeTypeException ex) {
            throw new InvalidMediaTypeException(ex);
        }
        try {
            return new MediaType(type.getType(), type.getSubtype(), type.getParameters());
        } catch (IllegalArgumentException ex) {
            throw new InvalidMediaTypeException(mediaType, ex.getMessage());
        }
    }

    public static class SpecificityComparator<T extends MimeType> implements Comparator<T> {

        @Override
        public int compare(T mimeType1, T mimeType2) {
            if (mimeType1.isWildcardType() && !mimeType2.isWildcardType()) {  // */* < audio/*
                return 1;
            } else if (mimeType2.isWildcardType() && !mimeType1.isWildcardType()) {  // audio/* > */*
                return -1;
            } else if (!mimeType1.getType().equals(mimeType2.getType())) {  // audio/basic == text/html
                return 0;
            } else {  // mediaType1.getType().equals(mediaType2.getType())
                if (mimeType1.isWildcardSubtype() && !mimeType2.isWildcardSubtype()) {  // audio/* < audio/basic
                    return 1;
                } else if (mimeType2.isWildcardSubtype() && !mimeType1.isWildcardSubtype()) {  // audio/basic > audio/*
                    return -1;
                } else if (!mimeType1.getSubtype().equals(mimeType2.getSubtype())) {  // audio/basic == audio/wave
                    return 0;
                } else {  // mediaType2.getSubtype().equals(mediaType2.getSubtype())
                    return compareParameters(mimeType1, mimeType2);
                }
            }
        }

        protected int compareParameters(T mimeType1, T mimeType2) {
            int paramsSize1 = mimeType1.getParameters().size();
            int paramsSize2 = mimeType2.getParameters().size();

            if (paramsSize1 == paramsSize2) {
                return 0;
            }
            if (paramsSize1 > paramsSize2) {
                return 1;
            }
            if (paramsSize1 < paramsSize2) {
                return -1;
            }
            return 0;
        }
    }

    public double getQualityValue() {
        String qualityFactor = getParameter(PARAM_QUALITY_FACTOR);
        return (qualityFactor != null ? Double.parseDouble(unquote(qualityFactor)) : 1D);
    }

    public static final Comparator<MediaType> SPECIFICITY_COMPARATOR = new SpecificityComparator<MediaType>() {

        @Override
        protected int compareParameters(MediaType mediaType1, MediaType mediaType2) {
            double quality1 = mediaType1.getQualityValue();
            double quality2 = mediaType2.getQualityValue();
            int qualityComparison = Double.compare(quality2, quality1);
            if (qualityComparison != 0) {
                return qualityComparison;  // audio/*;q=0.7 < audio/*;q=0.3
            }
            return super.compareParameters(mediaType1, mediaType2);
        }
    };

    public MediaType removeQualityValue() {
        if (!getParameters().containsKey(PARAM_QUALITY_FACTOR)) {
            return this;
        }
        Map<String, String> params = new LinkedHashMap<String, String>(getParameters());
        params.remove(PARAM_QUALITY_FACTOR);
        return new MediaType(this, params);
    }
}